/**
 * 
 */
package name.webdizz.gadget.four.envelope.client.view;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import name.webdizz.gadget.four.envelope.client.ImageBundleFactory;
import name.webdizz.gadget.four.envelope.client.StyleConstants;
import name.webdizz.gadget.four.envelope.client.domain.Envelope;
import name.webdizz.gadget.four.envelope.client.domain.EnvelopeSize;
import name.webdizz.gadget.four.envelope.client.domain.Expense;
import name.webdizz.gadget.four.envelope.client.domain.ExpenseItem;
import name.webdizz.gadget.four.envelope.client.domain.Person;
import name.webdizz.gadget.four.envelope.client.event.CreditExpenseEventHandler;
import name.webdizz.gadget.four.envelope.client.event.CreditGoalEventHandler;
import name.webdizz.gadget.four.envelope.client.event.SaveExpenseEventHandler;
import name.webdizz.gadget.four.envelope.client.locale.Locale;
import name.webdizz.gadget.four.envelope.client.locale.LocaleFactory;
import name.webdizz.gadget.four.envelope.client.util.Dates;
import name.webdizz.gadget.four.envelope.client.view.widgets.ExpenseListUi;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;

/**
 * Displays envelope data for appropriate account.
 * 
 * @author Izzet_Mustafayev
 * 
 */
public class EnvelopeView extends Composite implements IEnvelopeView {

	/**
	 * Represents panel.
	 */
	private FlexTable table;

	/**
	 * Holds current row number.
	 */
	private int rowNum = 0;

	/**
	 * The {@link SaveExpenseEventHandler} instance.
	 */
	private SaveExpenseEventHandler saveExpenseHandler;

	/**
	 * The {@link CreditGoalEventHandler} instance.
	 */
	private CreditGoalEventHandler creditGoalEventHandler;

	/**
	 * The {@link CreditExpenseEventHandler} instance.
	 */
	private CreditExpenseEventHandler creditExpenseEventHandler;

	/**
	 * {@link Person} list.
	 */
	private List<Person> persons;

	/**
	 * Holds {@link Envelope}'s table view to fast access for modifucations.
	 */
	private List<Map<Integer, Expense>> tableView = new ArrayList<Map<Integer, Expense>>(
			8);
	/**
	 * The {@link Envelope} size.
	 */
	EnvelopeSize envelopeSize = new EnvelopeSize();

	/**
	 * The {@link Locale}.
	 */
	private static final Locale locale = LocaleFactory.instance();

	/**
	 * Create {@link EnvelopeView} instance.
	 */
	public EnvelopeView() {
		table = new FlexTable();
		table.setCellSpacing(1);
		table.setWidth("60%");
		table.setHeight("200px");
		// prepare table rows
		for (int i = 0; i < 7; i++) {
			table.setHTML(i, 0, "");
		}
		initWidget(table);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * name.webdizz.gadget.four.envelope.client.view.IEnvelopeView#showEnvelope
	 * (name.webdizz.gadget.four.envelope.client.domain.Envelope)
	 */
	public void showEnvelope(final Envelope envelope) {
		// nulling EnvelopeSize
		envelopeSize.nulling();
		envelopeSize.setTotal(envelope.getSize());
		// clear table before proceed
		table.clear(true);
		// table.setBorderWidth(1);
		persons = envelope.getPersons();
		Label dateLabel = new Label(locale.date());
		dateLabel.addStyleName(StyleConstants.BOLD);
		table.setWidget(rowNum, 0, dateLabel);
		table.getCellFormatter().setWidth(rowNum, 0, "40px");
		tableView.add(rowNum, null);
		table.getCellFormatter().setHorizontalAlignment(rowNum, 0,
				HasHorizontalAlignment.ALIGN_CENTER);
		int personCell = 1;
		int personRow = rowNum++;
		for (Person person : persons) {
			Label personLabel = new Label(person.getName());
			personLabel.addStyleName(StyleConstants.BOLD);
			table.setWidget(personRow, personCell, personLabel);
			table.getCellFormatter().setHorizontalAlignment(personRow,
					personCell, HasHorizontalAlignment.ALIGN_RIGHT);
			List<Expense> expensesList = person.getExpenseList();
			for (Expense expense : expensesList) {
				showDate(expense, envelope.getExpenses(), envelope.getGoals());
				showExpense(personCell, expense);
				showEditExpense(personCell, expense);
				populateTableView(personCell, expense);
				calculateEnvelopeSpentSize(expense);
				rowNum++;
			}
			rowNum -= 7;
			personCell += 2;
		}
	}

	/**
	 * Calculates {@link EnvelopeSize};
	 * 
	 * @param expense
	 *            the {@link Expense} to include in calculations
	 */
	private void calculateEnvelopeSpentSize(final Expense expense) {
		envelopeSize.increaseSpent(expense.getAmount());
	}

	/**
	 * Gather data to fill table view of the envelope.
	 * 
	 * @param personCell
	 *            the {@link Person}'s cell
	 * @param expense
	 *            the {@link Expense}
	 */
	private void populateTableView(final int personCell, final Expense expense) {
		Map<Integer, Expense> rowView = new HashMap<Integer, Expense>();
		rowView.put(Integer.valueOf(personCell), expense);
		try {
			if (null != tableView.get(rowNum)) {
				tableView.get(rowNum).putAll(rowView);
			}
		} catch (IndexOutOfBoundsException exc) {
			tableView.add(rowNum, rowView);
		}
	}

	/**
	 * Replicate changes after {@link Expense} was updated successfully.
	 * 
	 * @param expense
	 *            the updated {@link Expense}
	 */
	public void replicateAfterExpenseUpdated(final Expense expense) {
		if (null != expense) {
			Expense oldExpense = null;
			int rowIndex = 0;
			int cell = 0;
			// resolve row and cell index
			for (Map<Integer, Expense> row : tableView) {
				if (null == row) {
					continue;
				}
				Set<Integer> expenseCell = row.keySet();
				for (Integer cellKey : expenseCell) {
					Expense currExpense = row.get(cellKey);
					if (expense.getDate().equals(currExpense.getDate())
							&& expense.getPersonId().equals(
									currExpense.getPersonId())) {
						oldExpense = currExpense;
						cell = cellKey.intValue();
						break;
					}
				}
				rowIndex++;
				if (cell > 0) {
					break;
				}
			}
			// update view
			Widget widget = table.getWidget(rowIndex, cell);
			if (null != widget) {
				if (widget instanceof Label) {
					((Label) widget).setText(expense.getAmount().toString());
				}
			}
			// replicate changes to envelope size to
			Expense calculateExpense = expense;
			if (null != oldExpense) {
				calculateExpense.setAmount(calculateExpense.getAmount()
						- oldExpense.getAmount());
			}
			calculateEnvelopeSpentSize(calculateExpense);
		}
	}

	/**
	 * Display edit icon click on which opens expense editing dialog.
	 * 
	 * @param personIndex
	 *            the {@link Person} cell index within table
	 * @param expense
	 *            the {@link ExpenseItem} to get value from
	 */
	private void showEditExpense(final int personIndex, final Expense expense) {
		final Image editIcon = new Image(ImageBundleFactory
				.createEnvelopeImageBundle().editIcon());
		editIcon.setTitle(locale.editDayExpense());
		editIcon.addStyleName(StyleConstants.CURSOR_POINTER);
		editIcon.addClickHandler(new ClickHandler() {
			public void onClick(final ClickEvent event) {
				createExpenseEditor(editIcon, expense);
			}
		});
		table.setWidget(rowNum, personIndex + 1, editIcon);
	}

	/**
	 * @param image
	 * @param expense
	 */
	private void createExpenseEditor(final Image image, final Expense expense) {
		final DialogBox expenseEditorPanel = new DialogBox(false, true);
		FlexTable expenseEditor = new FlexTable();
		int index = 0;
		Label exampleString = new Label(locale.expressionExample());
		expenseEditor.setWidget(index++, 0, exampleString);
		String expenseValue = expense.getExpense();
		if (null != expenseValue && expenseValue.trim().length() > 0) {
			expenseValue += " ";
		}
		final TextArea editor = new TextArea();
		editor.setVisibleLines(3);
		editor.setValue(expenseValue);
		expenseEditor.setWidget(index++, 0, editor);
		Button okButton = new Button(locale.ok(), new ClickHandler() {
			public void onClick(final ClickEvent event) {
				expense.setExpense(editor.getValue().trim());
				saveExpenseHandler.onSave(expense);
				expenseEditorPanel.hide();
			}
		});
		Button cancelButton = new Button(locale.cancel(), new ClickHandler() {
			public void onClick(final ClickEvent event) {
				expenseEditorPanel.hide();
			}
		});
		cancelButton.addStyleName(StyleConstants.EXPENSE_BUTTONS);
		HorizontalPanel buttons = new HorizontalPanel();
		buttons.add(okButton);
		buttons.add(cancelButton);
		buttons.setSpacing(8);
		expenseEditor.setWidget(index++, 0, buttons);
		expenseEditorPanel.setText(locale.dayExpenseTitle());
		expenseEditorPanel.setTitle(locale.dayExpenseTitle());
		expenseEditorPanel.setAnimationEnabled(true);
		expenseEditorPanel.setAutoHideEnabled(true);
		expenseEditorPanel.setPopupPositionAndShow(new PositionCallback() {
			public void setPosition(int offsetWidth, int offsetHeight) {
				expenseEditorPanel.setPopupPosition(image.getOffsetWidth(),
						offsetHeight - 10);
			}
		});
		expenseEditorPanel.add(expenseEditor);
		expenseEditorPanel.show();
		editor.setFocus(true);
		editor.setCursorPos(expenseValue.length());
	}

	/**
	 * Display envelope date information.
	 * 
	 * @param expense
	 *            the {@link Expense}
	 * @param expenses
	 *            the expenses as {@link ExpenseItem} list
	 * @param goals
	 *            the goals as {@link ExpenseItem} list
	 */
	private void showDate(final Expense expense,
			final Map<String, List<ExpenseItem>> expenses,
			final Map<String, List<ExpenseItem>> goals) {
		final Label label = new Label(Dates.getReadableDate(expense.getDate()));
		// highlight today
		if (Dates.TODAY.equals(expense.getDate())) {
			label.addStyleName(StyleConstants.BOLD);
		}
		// highlight have goals or expenses
		if (expenses.containsKey(expense.getDate())
				|| goals.containsKey(expense.getDate())) {
			label.addStyleName(StyleConstants.UNDERLINE);
			label.addStyleName(StyleConstants.CURSOR_POINTER);
			// assign click handler to display expenses or goals
			label.addClickHandler(new ClickHandler() {
				public void onClick(final ClickEvent event) {
					createGoalsView(label, expense, expenses, goals);
				}
			});
		}
		table.getCellFormatter().setHorizontalAlignment(rowNum, 0,
				HasHorizontalAlignment.ALIGN_CENTER);
		table.getCellFormatter().setWordWrap(rowNum, 0, true);
		table.getCellFormatter()
				.addStyleName(rowNum, 0, StyleConstants.NO_WRAP);
		table.setWidget(rowNum, 0, label);
	}

	/**
	 * Display expense.
	 * 
	 * @param personIndex
	 *            the {@link Person} column number to display expense in
	 * @param expense
	 *            the {@link Expense}
	 */
	private void showExpense(final int personIndex, final Expense expense) {
		Float amount = expense.getAmount();
		String label = null;
		if (0.0 == amount.floatValue()) {
			label = "    -";
		} else {
			label = amount.toString();
		}
		table.getCellFormatter().setAlignment(rowNum, personIndex,
				HasHorizontalAlignment.ALIGN_CENTER,
				HasVerticalAlignment.ALIGN_MIDDLE);
		table.setWidget(rowNum, personIndex, new Label(label));
	}

	/**
	 * Create {@link DialogBox} to display goals/expenses to related date.
	 * 
	 * @param label
	 *            the label that relate to this view
	 * @param expense
	 *            the current expense to analyze
	 * @param expenses
	 *            the expenses items
	 * @param goals
	 *            the goals items
	 */
	private void createGoalsView(final Label label, final Expense expense,
			final Map<String, List<ExpenseItem>> expenses,
			final Map<String, List<ExpenseItem>> goals) {
		final DialogBox expensePanel = new DialogBox(false, true);
		ExpenseListUi expenseListPanel = new ExpenseListUi();
		List<ExpenseItem> expensItems = expenses.get(expense.getDate());
		List<ExpenseItem> goalItems = goals.get(expense.getDate());
		EnvelopeViewHelper.createExpenseListItem(expenseListPanel, goalItems,
				locale.goals(), creditGoalEventHandler);
		EnvelopeViewHelper.createExpenseListItem(expenseListPanel, expensItems,
				locale.expenses(), creditExpenseEventHandler);
		expensePanel.setText(locale.expenseGloalsTitle());
		expensePanel.setTitle(locale.expenseGloalsTitle());
		expensePanel.setAnimationEnabled(true);
		expensePanel.setAutoHideEnabled(true);
		expensePanel.setPopupPositionAndShow(new PositionCallback() {
			public void setPosition(int offsetWidth, int offsetHeight) {
				expensePanel.setPopupPosition(label.getOffsetWidth(),
						offsetHeight - 10);
			}
		});
		expensePanel.add(expenseListPanel);
		expensePanel.show();
	}

	/**
	 * @param saveExpenseHandler
	 *            the saveExpenseHandler to set
	 */
	public void setSaveExpenseHandler(
			final SaveExpenseEventHandler saveExpenseHandler) {
		this.saveExpenseHandler = saveExpenseHandler;
	}

	/**
	 * @param creditGoalEventHandler
	 *            the creditGoalEventHandler to set
	 */
	public void setCreditGoalEventHandler(
			final CreditGoalEventHandler creditGoalEventHandler) {
		this.creditGoalEventHandler = creditGoalEventHandler;
	}

	/**
	 * @param creditExpenseEventHandler
	 *            the creditExpenseEventHandler to set
	 */
	public void setCreditExpenseEventHandler(
			final CreditExpenseEventHandler creditExpenseEventHandler) {
		this.creditExpenseEventHandler = creditExpenseEventHandler;
	}

	/**
	 * @return the envelopeSize
	 */
	public EnvelopeSize getEnvelopeSize() {
		return envelopeSize;
	}

}
